{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# Flask 基础知识"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Hello World in Flask\r\n",
    "让我们通过创建一个输出“ Hello World”的简单应用程序来开始我们进入 Flask 的尝试。创建一个名为 main.py 的新文件，并在其中输入以下代码。\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    return 'Hello World'\r\n",
    "\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run()\r\n",
    "```    \r\n",
    "\r\n",
    "我们刚刚在 Flask 中创建了“ Hello World”应用程序。如果 main.py 中的代码对您来说没有意义，那也没关系。在接下来的部分中，我们将更详细地讨论所有内容。要运行 main.py，请在虚拟环境中输入以下命令。\r\n",
    "\r\n",
    "`python main.py`\r\n",
    "\r\n",
    "运行 main.py 文件将启动端口5000上由 flask 提供的开发服务器。现在打开你最喜欢的浏览器，访问 http://127.0.0.1:5000/，看看 Hello World 应用程序的运行情况。\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/hello_world-f9026729-df60-411e-8fe6-b2e349a5b674.png)\r\n",
    "\r\n",
    "若要停止服务器，请按 ctrl + c。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 实例化 Flask 应用\r\n",
    "\r\n",
    "每个flask应用程序必须有一个flask类的实例。该实例实际上是一个 WSGI (Web服务器网关接口)应用程序，这仅仅意味着 web 服务器将它接收到的所有请求传递给该实例进行进一步处理。我们实例化一个类 Flask 的对象如下:\r\n",
    "\r\n",
    "```\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "```\r\n",
    "\r\n",
    "在第1行中，我们从flask包装中导入一个名为 Flask 的类。\r\n",
    "\r\n",
    "在第2行中，我们通过传递 `__name__` argument 给 Flask 构造函数来实例化一个 `Flask` 对象。`Flask` 构造函数有一个必需的参数，这个参数就是 `application` 包的名称。大多数时候 `__name__` 是正确的值。Flask 使用应用程序包的名称来查找静态资产、模板等。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 创建路由 \r\n",
    "路由 `route` 是将 URL 绑定到视图函数的行为。视图函数只是一个响应请求的函数。在 Flask 中，我们使用由 `Flask` 实例(即app变量)提供的 `route` 装饰器(@开头的变量为装饰器名) 将 URL 关联到视图函数。下面是我们如何在 Flask 中创建一条路由。\r\n",
    "```\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    return 'Hello World'\r\n",
    "```\r\n",
    "此代码将 index() 视图函数 注册为应用程序 根URL(\"/\") 的处理程序。换句话说，每当应用程序接收到一个请求，其请求的路径是\"/\"时, index() 函数将被调用来完成请求。\r\n",
    "\r\n",
    "### 替代方法，您可以使用 add_url_rule() 方法\r\n",
    "```\r\n",
    "def index():\r\n",
    "    return 'Hello World'\r\n",
    "app.add_url_rule('/', 'index', index)\r\n",
    "```\r\n",
    "视图函数**必须返回一个字符串**。尝试返回其他内容将导致500的内部服务器错误。\r\n",
    "\r\n",
    "### 当路由中的 URL 以斜线(/)结尾时\r\n",
    "\r\n",
    "Flask 将把请求重定向到末尾带斜线的 URL。这意味着对 /career 的要求将被转向 /career/。\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/career/')\r\n",
    "def career():\r\n",
    "    return 'Career Page'\r\n",
    "```\r\n",
    "\r\n",
    "### 我们还可以将多个 url 映射到同一个视图函数\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/contact/')\r\n",
    "@app.route('/feedback/')\r\n",
    "def feedback():\r\n",
    "    return 'Feedback Page'\r\n",
    "```\r\n",
    "\r\n",
    "### 如果你试图访问一个没有映射到视图函数的 URL，你会得到一个404 Not Found 错误。\r\n",
    "\r\n",
    "\r\n",
    "### 动态 URL \r\n",
    "\r\n",
    "动态 URL 是由一个或多个影响页面输出的可变部分组成的 URL。\r\n",
    "\r\n",
    "我们可以将 URL 中的动态部分标记为 <variable_name> 。然后将动态部分作为关键字参数传递给 view 函数。下面的清单定义了一个包含动态部分的路由。\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/user/<id>/')\r\n",
    "def user_profile(id):\r\n",
    "    return \"Profile page of user #{}\".format(id)\r\n",
    "```\r\n",
    "\r\n",
    "### 转换器 `converter`\r\n",
    "默认情况下，URL 的动态部分作为字符串传递给视图函数。我们可以改变这一点,通过在 URL 的 动态部分之前指定一个转换器作为 `<converter:variable_name>` 来实现。\r\n",
    "例如,`/user/<int:id>/`route 将匹配如/user/1/、/user/2000/的URL。它将不匹配如 `/user/cowboy/`, /user/foobar10/ 和 `/user/@@##/` 的url。\r\n",
    "\r\n",
    "```\r\n",
    "@app.route('/user/<int:user_id>/')\r\n",
    "def user_profile(user_id):\r\n",
    "    return \"Profile page of user #{}\".format(user_id)\r\n",
    "```\r\n",
    "\r\n",
    "下表列出了 Flask 提供的所有转换器:\r\n",
    "\r\n",
    "| 转换器   | 描述                               |\r\n",
    "| :------- | :--------------------------------- |\r\n",
    "| `string` | 接受任何字符串，这是默认值         |\r\n",
    "| `int`    | 接受整数                           |\r\n",
    "| `float`  | 接受浮点指向值                     |\r\n",
    "| `path`   | 接受带有前导斜杠和前向斜杠的路径名 |\r\n",
    "| `uuid`   | 接受 uuid 字符串                   |\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 启动服务器\r\n",
    "要启动开发服务器，我们调用 Flask 对象的 run ()方法。\r\n",
    "\r\n",
    "```\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run()\r\n",
    "```\r\n",
    "\r\n",
    "条件 `__name__ == \"__main__\"` 确保只有当 main.py 作为主程序运行时才调用 run ()方法。如果在另一个 Python 模块中导入 main.py，则不会调用 run ()方法。\r\n",
    "\r\n",
    "注意: 开发服务器附带的 Flask 仅用于开发目的。因此，它将在生产中表现不佳。\r\n",
    "\r\n",
    "现在您应该对 `main.py` 的工作方式有了相当好的了解。\r\n",
    "\r\n",
    "\r\n",
    "\r\n",
    "## 调试模式\r\n",
    "\r\n",
    "编程中的缺陷是不可避免的，迟早你会引入一个。这就是为什么知道如何调试应用程序并快速修复错误是非常重要的。Flask 有一个强大的交互式网络调试器，但是默认关闭了。当调试器关闭并且 Flask 遇到任何错误时，它将显示500个内部服务器错误。让我们通过在 main.py 文件中有意地引入一个 bug 来了解这种行为。打开 main.py 并按如下方式修改文件(突出显示更改) :\r\n",
    "\r\n",
    "### flask_app/main.py\r\n",
    "```python\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    print(i)\r\n",
    "    return 'Hello World'\r\n",
    "\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run()\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "这里我们试图打印一个未定义变量 i 的值，所以我们一定会得到一个错误。如果服务器还没有运行，请启动服务器并访问 http://127.0.0.1:5000/。你应该看到 500的内部服务器错误 如下:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/500_Internal_Server_Error-1a550c2b-a2b9-4a48-bdf5-b2ac45988ffa.png)\r\n",
    "\r\n",
    "尽管如此，浏览器并没有提供任何错误类型的信息。如果查看运行服务器的 shell 的标准输出，您将看到刚刚发生的错误的回溯。在这种情况下，回溯看起来像这样:\r\n",
    "\r\n",
    "```\r\n",
    "File \"/home/overiq/flask_app/env/lib/python3.5/site-packages/flask/app.py\", line 1612, in full_dispatch_request\r\n",
    "    rv = self.dispatch_request()\r\n",
    "  File \"/home/overiq/flask_app/env/lib/python3.5/site-packages/flask/app.py\", line 1598, in dispatch_request\r\n",
    "    return self.view_functions[rule.endpoint](**req.view_args)\r\n",
    "  File \"main.py\", line 13, in index\r\n",
    "    print(i)\r\n",
    "NameError: name 'i' is not defined\r\n",
    "```\r\n",
    "\r\n",
    "另外，当调试模式被关闭时，在每次代码更改之后，您必须手动启动服务器以使更改生效。打开调试模式将在每次更改后自动重新启动服务器。\r\n",
    "\r\n",
    "\r\n",
    "\r\n",
    "要打开 debug 模式，请将 `debug = True` 传递给 run ()方法，如下所示:\r\n",
    "```\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run(debug=True)\r\n",
    "```\r\n",
    "\r\n",
    "另一种打开调试模式的方法是将 Flask 实例的调试属性设置为 True。\r\n",
    "\r\n",
    "```\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "app.debug = True\r\n",
    "```\r\n",
    "\r\n",
    "按如下方式修改 main.py (突出显示更改)并运行它。\r\n",
    "\r\n",
    "### flask_app/main.py\r\n",
    "```python\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    print(i)\r\n",
    "    return 'Hello World'\r\n",
    "\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run(debug=True)  # KEY LINE\r\n",
    "```\r\n",
    "\r\n",
    "再次访问 http://127.0.0.1:5000/调试器，这一次你应该看到如下 Flask debugger 在运行:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/debugger_page-6c9a31be-e262-4629-b186-0c5311b5a503.png)\r\n",
    "\r\n",
    "如果启用调试器后发生任何错误，您将看到问题的详细回溯，而不是一般的500 Internal Server Error。一般来说，回溯可以很好地指示所发生的问题。在页面的底部，我们可以看到 print 语句试图打印未定义变量 i 的值以及错误类型，这是 `NameError` 异常，告诉我们名字 i 没有定义。\r\n",
    "\r\n",
    "单击回溯中的一行代码将展开有问题代码的源代码。这对于在解释错误的同时建立一些语境很有帮助。\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/inspecting_errors-afb2be19-22f5-47fc-bfa1-9a778008ef59.png)\r\n",
    "\r\n",
    "您可能已经注意到，当您将鼠标放在回溯终端图标中的一行代码上时，将显示出来。单击 Terminal 图标将打开一个控制台，您可以在其中执行任何 Python 代码。\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/console_in_debugger-e5629edf-01e7-41aa-a1ac-d3e9229a621d.png)\r\n",
    "\r\n",
    "此控制台允许我们在发生异常时检查本地变量。\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/value_inspection_in_console_debugger-b57d6394-7091-4f1f-a221-d6bab0fae8c1.png)\r\n",
    "\r\n",
    "如果您是第一次打开控制台，它将提示您输入 PIN。\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/debugger_asking_for_a_pin-9792d29b-ec81-4614-97cf-57973ad23665.png)\r\n",
    "\r\n",
    "这是一种安全措施，仅容许授权用户对控制台的访问。要访问控制台，你必须输入正确的 PIN。您可以在运行服务器的 shell 的标准输出中找到 PIN。\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/pin_in_the_shell-c4f02bf2-034f-45f5-bf99-b5a0430c4fff.png)\r\n",
    "\r\n",
    "让我们通过创建另一个 Flask 应用程序来结束本课程，该应用程序实现了我们到目前为止学到的所有东西。\r\n",
    "\r\n",
    "创建另一个名为 main2.py 的文件，代码如下:\r\n",
    "\r\n",
    "### flask_app/main2.py\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    return 'Hello Flask'\r\n",
    "\r\n",
    "@app.route('/user/<int:user_id>/')\r\n",
    "def user_profile(user_id):\r\n",
    "    return \"Profile page of user #{}\".format(user_id)\r\n",
    "\r\n",
    "@app.route('/books/<genre>/')\r\n",
    "def books(genre):\r\n",
    "    return \"All Books in {} category\".format(genre)\r\n",
    "\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run(debug=True)\r\n",
    "```\r\n",
    "\r\n",
    "运行文件并访问 http://localhost:5000/ ，你会看到如下的“ Hello Flask”:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/hello_flask-ade38e8b-edd4-4700-8512-d8031fbc1234.png)\r\n",
    "\r\n",
    "这个新版本的应用程序添加了两个动态的新路由。让我们来测试一下。在你的浏览器地址栏中输入 http://localhost:5000/user/123/ ，你应该会得到这样的回复:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/user_profile_page-27691f53-805f-4d8d-8b15-2cff4d7bd1bd.png)\r\n",
    "\r\n",
    "请注意，`/user/<int:user_id>/` 将只匹配动态部分(即 user_id)为整数的 url。\r\n",
    "\r\n",
    "为了测试第二条动态路线，请访问 http://localhost:5000/books/sci-fi/ ，这一次你应该得到如下回复:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/books_genre_page-80f65ce4-f305-4483-920b-56c0068616bd.png)\r\n",
    "\r\n",
    "在这一点上，如果你尝试访问一个没有在路径中定义的 URL，你会得到一个404 Not Found Error。例如，在访问 http://localhost:5000/products 时，应用程序会响应一个404错误，如下:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/6/404_error-78c72988-5d9c-4fc4-ab29-35abfdecd85e.png)"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## FLASK如何处理一个请求？\r\n",
    "\r\n",
    "那么，当 Flask 接收到客户端的请求时，它如何知道要执行哪个视图函数？\r\n",
    "\r\n",
    "FLASK 内部维护了要执行的 url 和视图函数的映射。这个映射是使用 Flask 实例的 `route` 装饰器 或 `add_url_rule()` 方法创建的。我们可以使用 Flask 实例的 `url_map` 属性来访问这个映射。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "source": [
    "from main2 import app\r\n",
    "app.url_map"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Map([<Rule '/' (HEAD, GET, OPTIONS) -> index>,\n",
       " <Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>,\n",
       " <Rule '/books/<genre>/' (HEAD, GET, OPTIONS) -> books>,\n",
       " <Rule '/user/<user_id>/' (HEAD, GET, OPTIONS) -> user_profile>])"
      ]
     },
     "metadata": {},
     "execution_count": 1
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "\r\n",
    "如输出所示，映射中有 4 个规则。 Flask 按以下格式创建 URL 映射：\r\n",
    "\r\n",
    "`url pattern, (comma separated list of HTTP methods handled by the route) -> view function to execute`\r\n",
    "\r\n",
    "路由 `/static/<filename>` 由 Flask 自动添加，用于提供静态文件。 我们将在服务静态文件课程中讨论如何提供静态文件。\r\n"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python",
   "version": "3.9.6",
   "mimetype": "text/x-python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python",
   "file_extension": ".py"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.9.6 64-bit"
  },
  "interpreter": {
   "hash": "c644d696b95f5e0f4df3c6556741cf30bcf9ea6ca93c3e1f29fcf31d885534fc"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}